<?php
/*********************************************
 *  CPG Dragonfly™ CMS
 *********************************************
	Copyright © since 2010 by CPG-Nuke Dev Team
	http://dragonflycms.org

	Dragonfly is released under the terms and conditions
	of the GNU GPL version 2 or any later version

	http://www.w3.org/TR/css3-images/#linear-gradients
*/

namespace Poodle\Kernels;

class CSS extends PushFiles
{
	protected
		$f_ext = '.css',
		$mime  = 'text/css',
		$cfg   = array(
			'cache_dir'   => null,
			'cache_uri'   => null,
			'design_mode' => false,
			'expires'     => 8035200, // 3 months
			'max_embed_size' => 5800, // in bytes
			'style_mode'  => 'normalize', // or reset
		),

		$engine  = 'unknown', # moz, khtml, opera, ms, webkit
		$version = 0,
		$embed,
		$ua_re,
		$IE_Extensions = '(accelerator|behavior|filter|ime-mode|interpolation-mode|scrollbar-(?:3dlight|arrow|base|darkshadow|face|highlight|shadow|track)-color|text-underline-position|zoom)',

		# http://www.w3.org/TR/css3-userint is superseded so there is no 'user-*'
		$CSS3 = '(animation[a-z\-]*|background-size|box-shadow|box-sizing|column-(count|rule|gap)|columns|text-(fill|overflow|resize|shadow|stroke)|transform|transition(-[a-z\-]+)?|border-radius(-[a-z\-]+)?)';

	function __construct(array $cfg, $path='')
	{
		$keys = $path ? $path : \Poodle::$PATH;
		if (3!=count($keys)
		 || !preg_match('#^[a-z0-9_\-]+$#',$keys[1])
		 || !is_dir('tpl/'.$keys[1].'/css')
		 || !preg_match('#^([a-z0-9_\-]+(;|$))+$#D', $keys[2])
		) {
			\Poodle\HTTP\Headers::setStatus(412);
			exit(\Poodle\HTTP\Status::get(412));
		}

		parent::__construct($cfg);

		$this->embed = !stripos($_SERVER['HTTP_USER_AGENT'], 'iPhone');
		$ETag  = null;
		$ua_re = array('moz'=>'moz','khtml'=>'khtml','opera'=>'o','ms'=>'ms\d*','webkit'=>'webkit');
		if (stripos($_SERVER['HTTP_USER_AGENT'], 'Opera') !== false) {
			$this->engine = 'opera';
//			$this->version = Version/10.60
		} else
		if (stripos($_SERVER['HTTP_USER_AGENT'], 'WebKit/') !== false) {
			$this->engine = 'webkit';
		} else
		if (stripos($_SERVER['HTTP_USER_AGENT'], 'KHTML/') !== false) {
			$this->engine = 'khtml';
		} else
		if (stripos($_SERVER['HTTP_USER_AGENT'], 'Gecko/') !== false) {
			$this->engine = 'moz';
		} else
		if (preg_match('#MSIE (1?[0-9])#', $_SERVER['HTTP_USER_AGENT'], $match)) {
			$this->engine = 'ms';
			$this->version = (int)$match[1];
			$this->msie_re = '#-ms'.($this->version>10 ? '([5-9|1[0-'.($this->version-10).'])' : '[5-'.($this->version-1).']').'-[^;]+;#';
			$ETag = 'msie'.$this->version;
			$this->embed = $this->version>=8;
		} else {
			$this->embed = false;
		}
		unset($ua_re[$this->engine]);
		$this->ua_re = $ua_re;
		$this->tpl   = $keys[1];

		$this->dirs = array(
			self::$DIR_BASE."tpl/{$this->tpl}/css",
			self::$DIR_BASE.'tpl/default/css'
		);

		$files = $this->getValidFilesList($keys[2]);
		ksort($files);

		if (isset($files['style'])) {
			$f = $files['style'];
			unset($files['style']);
			array_unshift($files, $f);
			unset($files['reset']);
			unset($files['normalize']);
			if ('normalize'!==$this->cfg['style_mode']) {
				$f = $this->findFile('reset');
				if (!$f) $f = $this->findFile('poodle_reset');
			} else {
				$f = $this->findFile('normalize');
				if (!$f) $f = $this->findFile('poodle_normalize');
			}
			array_unshift($files, $f);
		}

		if (!$this->cfg['expires'] || $this->cfg['design_mode']) {
			$this->cfg['expires'] = 0;
			foreach ($files as $file) { $this->mtime = max($this->mtime, @filemtime($file)); }
		}

		if (!$ETag) { $ETag = $this->engine; }
		$this->CFile = "tpl/{$this->tpl}/css/{$ETag}-" . md5(implode(';', $files)) . ($this->cfg['design_mode']?'-1':'-0');
		$this->ETag  = str_replace('/','-',$this->CFile);
		$this->files = $files;
	}

	protected function findFile($filename)
	{
		$ext = $this->f_ext;
		if ($file = \Poodle::getFile($filename.$ext, $this->dirs)) { return $file; }
		else if ($file = \Poodle::getFile(str_replace('_','/',$filename).$ext, $this->dirs)) { return $file; }
		else {
			$path = explode('_', $filename);
			$lib  = array_shift($path); // library
			$comp = $path ? array_shift($path) : $lib; // component
			if (!isset($path[0])) $path[0] = $comp;
			if ($file = \Poodle::getFile("{$lib}/{$comp}/tpl/css/".(isset($path[0])?implode('/',$path):$comp).$ext)) { return $file; }
			else if ($file = \Poodle::getFile("{$lib}/{$comp}/tpl/".(isset($path[0])?implode('/',$path):$comp).$ext)) { return $file; }
			else if ($file = \Poodle::getFile("{$lib}/css/{$comp}".($path[0]!=$comp?'_'.implode('_',$path):'').$ext)) { return $file; }
		}
		return false;
	}

	public function run()
	{
		$this->send_file();

		// file didn't exist yet, failed or needs updating
		$TPL_URI = self::$URI_BASE."/tpl/{$this->tpl}/";

		$ie_ex_re  = ('ms' !== $this->engine && 'unknown' !== $this->engine) ? "#([;{]){$this->IE_Extensions}[^;]+#" : null;
		$str_re    = '#"[^\n"\\\\]*(?:\\\\.[^\n"\\\\]*)*"|\'[^\n\'\\\\]*(?:\\\\.[^\n\'\\\\]*)*\'*#';
		$ua_re     = '#([a-z-]+:\s*|-poodle-fix)?-('.implode('|', $this->ua_re).')-[^;{}]+;#';
		$ua_engine = 'ua_'.$this->engine;
		$image_cb  = array($this,'embed_image');

		$data = "/* CPG Dragonfly™ CMS, Copyright © since 2010 by CPG-Nuke Dev Team. All rights reserved. */\n";
		if ($this->missing) { $data .= "/* Not found:\n\t".implode("\n\t",$this->missing)."\n*/\n"; }
//		$data .= "/* Found:\n\t".implode("\n\t",$this->files)."\n*/\n";
		foreach ($this->files as $file)
		{
			$buffer = file_get_contents($file);

			// Minify data
			$buffer = preg_replace('#/\*.*?\*/#s','',$buffer);
			$buffer = preg_replace('#@import[^;]*;#','',$buffer);
			preg_match_all($str_re, $buffer, $strings);
			$strings = $strings[0];
			$buffer = preg_split($str_re, $buffer);
/*			$buffer = preg_replace('#(?:([^a-z0-9*])\s+|\s+([^a-z0-9*]))#i', '$1$2', $buffer);
			$buffer = preg_replace('#([^a-z0-9*])\s+#i', '$1', $buffer);
			$buffer = preg_replace('#\s+([^a-z0-9*])#i', '$1', $buffer);
*/			$buffer = preg_replace('#\s+#', ' ', $buffer);
			$buffer = preg_replace('#([:\s]+0)(em|ex|px|in|cm|mm|pt|pc|%)#i', '$1', $buffer);
			$buffer = preg_replace('#\s*([{},;:])\s*#', '$1', $buffer);
			$buffer = preg_replace('#([^;}])}#','$1;}',$buffer);

			// Merge strings with minified data
			$c = count($strings);
			for ($i = 0; $i < $c; ++$i) { $buffer[$i] .= $strings[$i]; }
			$buffer = implode('', $buffer);

			$buffer = preg_replace($ua_re, '', $buffer);
			if ($ie_ex_re)
				$buffer = preg_replace($ie_ex_re, '$1', $buffer);

			$this->$ua_engine($buffer);

			$buffer = preg_replace('#-poodle-fix-[^-]+-#','',$buffer);

			$buffer = preg_replace('#;+;#',';',$buffer);
			// Semicolon after last declaration not needed
			$buffer = preg_replace('#;+}#','}',$buffer);

			// Remove selectors with empty declarations
			$buffer = preg_replace('#[^{}]+{}#','',$buffer);

			$buffer = preg_replace('#url\((["\']?)(../)?([a-z])#', 'url($1'.$TPL_URI.'$3', $buffer);
			$buffer = preg_replace('#url\((["\']?)/inc/#', 'url($1'.self::$URI_BASE.'/inc/', $buffer);
			$buffer = preg_replace_callback('#url\((["\']?)'.self::$URI_BASE.'(/[a-z0-9/_\-]+\.(png|jpe?g|gif|cur))\\1\)#', $image_cb, $buffer);
			$data .= trim($buffer);
			$buffer = '';
		}

		$debug = '';
		$images = $this->embed_image();
		foreach ($images as $img => $c) { if ($c>1) $debug .= "$img:$c\n"; }
		if ($debug) $data .= "\n/*\n$debug*/";
		if ($this->cfg['design_mode']) { $data = preg_replace('#([}]+)#', "\$1\n", $data); }

//		echo $data;
//		print_r($this);
//		exit;
		$this->send_file($data);
	}

	public function embed_image($img=null)
	{
		static $count = array();
		if (null === $img) { return $count; }
		$ext = $img[3];
		$img = $img[2];
		if (!isset($count[$img])) $count[$img] = 0;
		++$count[$img];

		$file = self::$DIR_BASE.$img;
		if (!is_file($file)) {
			$img = preg_replace('#/tpl/[^/]+/#', '/tpl/default/', $img);
			$file = self::$DIR_BASE.$img;
			if (!is_file($file)) { return "none/*missing {$img}*/"; }
		}
		$img = self::$URI_BASE.$img;
		if ($this->embed && filesize($file)<$this->cfg['max_embed_size'])
		{
			if ('jpg' === $ext) { $ext = 'jpeg'; }
			if ('cur' === $ext) { $ext = 'x-cursor'; }
			$img = 'data:image/'.$ext.';base64,'.base64_encode(file_get_contents($file));
		}
		return "url({$img})";
	}

	/**
	 * Create two versions of attributes,
	 * like: border-radius & -moz-border-radius
	 */
	protected function fix_css3($attrs, $prefix, &$buffer)
	{
		# Must be seperate in this order or it fails
		$buffer = preg_replace('#;('.$attrs.'):([^;}]+)#', ';-'.$prefix.'-$1:$2;$1:$2', $buffer);
		$buffer = preg_replace('#{('.$attrs.'):([^;}]+)#', '{-'.$prefix.'-$1:$2;$1:$2', $buffer);
	}

	# Mozilla Gecko
	protected function ua_moz(&$buffer)
	{
		self::fix_css3('background-size|border-radius|border-[a-z]+-colors|box-shadow|box-sizing|column-(?:[a-z-]+)|columns|transform|transition|user-select|user-modify', 'moz', $buffer);
		$buffer = preg_replace('#([{;])border-(top|bottom)-(left|right)-radius:#', '$1-moz-border-radius-$2$3:', $buffer);
		# Pseudo class :placeholder for input and textarea doesn't exist yet
		$buffer = str_replace(':placeholder', ':-moz-placeholder', $buffer);
		# Firefox < 16 -moz-linear-gradient
		$buffer = preg_replace('/(background-image:linear-gradient\\(([^;}]+))/', 'background-image:-moz-linear-gradient($2;$1', $buffer);
	}

	protected function ua_khtml(&$buffer)
	{
		self::fix_css3('background-size|border-radius|border-[a-z]+-colors|box-shadow|transform', 'khtml', $buffer);
	}

	# presto
	protected function ua_opera(&$buffer)
	{
		# CSS3, 10.5=border-radius
		self::fix_css3('background-size|border-radius|box-shadow|transform', 'o', $buffer);
/*		Bug in Opera 10.60 makes them hidden
		if (preg_match_all('#border-radius:([^\s;]+)\s+([^\s;]+)\s+([^\s;]+)\s+([^\s;]+)\s*;#', $buffer, $matches, PREG_SET_ORDER)) {
			foreach ($matches as $m) $buffer = str_replace($m[0], "border-top-left-radius:{$m[1]};border-top-right-radius:{$m[2]};border-bottom-right-radius:{$m[3]};border-bottom-left-radius:{$m[4]}", $buffer);
		}*/
//		$buffer = preg_replace('#border-radius[^;}]+#', '', $buffer);
	}

	protected function ua_webkit(&$buffer)
	{
		# CSS3
		self::fix_css3('background-size|border-radius|border-[a-z]+-colors|box-shadow|column-(?:[a-z-]+)|columns|transform|transition|user-select|user-modify|appearance', 'webkit', $buffer);
		# Pseudo class :placeholder for input and textarea doesn't exist yet
		$buffer = str_replace(':placeholder', '::-webkit-input-placeholder', $buffer);
	}

	protected function ua_unknown(&$buffer)
	{
	}

	# Internet Explorer Trident
	protected function ua_ms(&$buffer)
	{
		$buffer = preg_replace($this->msie_re, '', $buffer);
		$buffer = preg_replace('#-ms\d*-#', '', $buffer);

		# CSS3
		self::fix_css3('box-sizing', 'ms', $buffer);

		if (9>$this->version) {
			# msdn.microsoft.com/en-us/library/ms531207%28VS.85%29.aspx
			$s = '(?:([0-9-]+)[^0-9-;#]+)?';
			if (preg_match_all('@box-shadow:'.$s.$s.$s.$s.'([^;]*);@', $buffer, $matches, PREG_SET_ORDER))
			{
				foreach ($matches as $match) {
					$v = '';
					if ($match[5] && '#'===$match[5][0]) {
						// Seems to fail
//						$v = "filter:DropShadow(color=".preg_replace('@#([a-z0-9])([a-z0-9])([a-z0-9])$@i','#$1$1$2$2$3$3',$match[5]).", offx={$match[1]}, offy={$match[2]}, Strength={$match[3]});";
					}
					$buffer = str_replace($match[0], $v, $buffer);
				}
			}
			# blogs.msdn.com/ie/archive/2008/09/08/microsoft-css-vendor-extensions.aspx
			if (7<$this->version) {
				# IE Extension
				$buffer = preg_replace("#([;{])({$this->IE_Extensions})#", '$1-ms-$2', $buffer);
				# CSS3
				$buffer = preg_replace('#([;{])(background-position-[xy]|block-progression|layout-grid(-char|-line|-mode|-type)|line-(break|grid-mode)|overflow-[xy]|text-(align-last|autospace|justify|kashida-space|overflow)|word-(break|wrap)|writing-mode)#', '$1-ms-$2', $buffer);
			} else {
				$buffer = str_replace('-ms-','',$buffer);
			}
			// progid:DXImageTransform.Microsoft.
			# http://css-tricks.com/rgba-browser-support/
			$buffer = preg_replace('#background-color:rgba\((\d+),(\d+),(\d+),([\d\.]+)\)#e', '\'filter:Gradient(StartColorStr=#\'.sprintf(\'%02X%02X%02X%02X\',(int)($4*255),$1,$2,$3).\',EndColorStr=#\'.sprintf(\'%02X%02X%02X%02X\',(int)($4*255),$1,$2,$3).\')\'', $buffer);
			# Fails in IE8 on some elements so fall back
			$buffer = preg_replace('#opacity:([0-9\.]+)#e', '\'filter:Alpha(opacity=\'.(int)($1*100).\')\'', $buffer);

			$buffer = preg_replace_callback('#{([^{}]*filter:[^{}]+)}#s', array($this, 'ua_ms_filter'), $buffer);

			// CSS3 pseudo-classes make IE8 ignore the whole rule, so we remove them
			// http://www.quirksmode.org/css/contents.html
			$selectors = 'target|nth-|last-|first-of-|only-|root|empty|target|enabled|disabled|checked|not';
			$buffer = preg_replace('#,[^,{}]+:('.$selectors.')[^,{]*#s', '', $buffer);
			$buffer = preg_replace('#}[^,{}]+:('.$selectors.')[^,{]*,#s', '}', $buffer);
			$buffer = preg_replace('#}[^,{}]+:('.$selectors.')[^,{]*{[^}]*}#s', '}', $buffer);
		}
/*
		$media_re = '#(@[^{]+){((?:[^{}]+{[^{}]+})*)}#';
		preg_match_all($media_re,$buffer, $media);
		unset($media[0]);
		$buffer = preg_split($media_re, $buffer);
		$c = max(count($media),count($buffer));
		for ($i=0; $i<$c; ++$i)
		{
			if (isset($buffer[$i]))   { $data .= $buffer[$i]; }
			if (isset($media[1][$i])) { $data .= $media[1][$i].'{'.$media[2][$i].'}'; }

			preg_match_all('#([^{]+){([^}]+)}}?#',$buffer[$i], $matches);
			print_r($matches);
		}
*/
	}
	protected function ua_ms_filter($decls)
	{
		$decls = $decls[1];
		$filters = array();
		if (preg_match_all('#filter:([^;]+);#', $decls, $matches))
		{
			$decls = preg_replace('#filter:([^;]+);#', '', $decls);
			$decls = 'filter:'.implode(',',$matches[1]).';'.$decls;
			// Filter needs hasLayout http://msdn.microsoft.com/en-us/library/ms530764%28v=vs.85%29.aspx
			if (!preg_match('#((height|width|zoom):|float:(left|right)|position:absolute)#',$decls))
				$decls = 'zoom:1;'.$decls;
		}
		return '{'.$decls.'}';
	}

}
